package BeanUtils

import (
	"errors"
	"reflect"
	"strings"
)

func CopyProperties(source, typ interface{}, handle ...Handler) (interface{}, error) {
	if source == nil {
		return nil, errors.New("source is nil")
	}
	sourceVal, destTyp := reflect.Indirect(reflect.ValueOf(source)), reflect.TypeOf(typ)
	if sourceVal.Kind() != reflect.Slice ||
		(reflect.Indirect(reflect.ValueOf(sourceVal.Type().Elem())).Kind() != reflect.Struct) {
		return nil, errors.New("source type error")
	}
	var result reflect.Value
	if destTyp.Kind() == reflect.Ptr {
		result = reflect.MakeSlice(reflect.SliceOf(destTyp), sourceVal.Len(), sourceVal.Len())
	} else {
		result = reflect.MakeSlice(reflect.SliceOf(reflect.PtrTo(destTyp)), sourceVal.Len(), sourceVal.Len())
	}
	DeepCopyFields(sourceVal, result, destTyp, handle...)
	return result.Interface(), nil
}

func DeepCopyFields(source, result reflect.Value, destTyp reflect.Type, handle ...Handler) {
	for i := 0; i < source.Len(); i++ {
		sour := source.Index(i)
		dest := reflect.New(getElementType(destTyp))
		if dest.IsValid() || dest.IsNil() {
			if source.Index(i).Kind() == reflect.Ptr {
				sour = source.Index(i).Elem()
			}
			copyProperty(sour.Interface(), dest.Interface())
			if len(handle) == 1 {
				handle[0](source.Interface(), dest.Interface())
			}
			result.Index(i).Set(dest)
		}
	}
}

func CopyProperty(source, dest any, handle ...func(interface{}) error) error {
	sourceVal, destVal := reflect.Indirect(reflect.ValueOf(source)), reflect.TypeOf(dest)
	if kin := sourceVal.Type(); kin.Kind() != reflect.Struct ||
		(kin.Kind() == reflect.Ptr && (reflect.Indirect(sourceVal.Elem()).Kind() != reflect.Struct)) {
		return errors.New("源参数非结构体类型")
	}
	if destVal.Kind() != reflect.Ptr {
		return errors.New("目标对象非结构体指针")
	}
	copyProperty(source, dest)
	return func() error {
		if len(handle) > 0 {
			return handle[0](dest)
		}
		return nil
	}()
}

// CopyMapToStruct copies map data to a struct
func CopyMapToStruct(data map[string]interface{}, result interface{}) error {
	val := reflect.ValueOf(result).Elem()
	for key, value := range data {
		fieldName := key
		field := val.FieldByNameFunc(func(s string) bool {
			return strings.EqualFold(s, fieldName)
		})
		if field.IsValid() && field.CanSet() {
			field.Set(reflect.ValueOf(value))
		}
	}
	return nil
}
